/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.wdm.beans;

import java.io.Serializable;
import org.wdm.beans.points.Point;

/**
 * A BoundingBox represents an immutable set of two latitude and two longitude
 * coordinates.
 */
public class BoundingBox implements Serializable {

    private static final long serialVersionUID = 1L;

    private static boolean isBetween(double number, double min, double max) {
        return min <= number && number <= max;
    }
    public double maxLatitude;
    public double maxLongitude;
    public double minLatitude;
    public double minLongitude;

    
    BoundingBox() {
        this.minLatitude = 0.0;
        this.minLongitude = 0.0;
        this.maxLatitude = 0.0;
        this.maxLongitude = 0.0;
    }
    
    public BoundingBox(Point start, Point end) {
        if (start.getLatitude() >= end.getLatitude()) {
            if (start.getLongitude() >= end.getLongitude()) {
                this.minLatitude = end.getLatitude();
                this.minLongitude = end.getLongitude();
                this.maxLatitude = start.getLatitude();
                this.maxLongitude = start.getLongitude();
            } else {
                this.minLatitude = start.getLatitude();
                this.minLongitude = end.getLongitude();
                this.maxLatitude = end.getLatitude();
                this.maxLongitude = start.getLongitude();
            }
        } else {
            if (start.getLongitude() >= end.getLongitude()) {
                this.minLatitude = start.getLatitude();
                this.minLongitude = end.getLongitude();
                this.maxLatitude = end.getLatitude();
                this.maxLongitude = start.getLongitude();
            } else {
                this.minLatitude = start.getLatitude();
                this.minLongitude = start.getLongitude();
                this.maxLatitude = end.getLatitude();
                this.maxLongitude = end.getLongitude();
            }
        }
    }

    /**
     * @param point the point whose coordinates should be checked.
     * @return true if this BoundingBox contains the given GeoPoint, false
     * otherwise.
     */
    public boolean contains(Point point) {
        return isBetween(point.getLatitude(), this.minLatitude, this.maxLatitude)
                && isBetween(point.getLongitude(), this.minLongitude, this.maxLongitude);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof BoundingBox)) {
            return false;
        }
        BoundingBox other = (BoundingBox) obj;
        if (this.maxLatitude != other.maxLatitude) {
            return false;
        } else if (this.maxLongitude != other.maxLongitude) {
            return false;
        } else if (this.minLatitude != other.minLatitude) {
            return false;
        } else if (this.minLongitude != other.minLongitude) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 97 * hash + (int) (Double.doubleToLongBits(this.maxLatitude) ^ (Double.doubleToLongBits(this.maxLatitude) >>> 32));
        hash = 97 * hash + (int) (Double.doubleToLongBits(this.maxLongitude) ^ (Double.doubleToLongBits(this.maxLongitude) >>> 32));
        hash = 97 * hash + (int) (Double.doubleToLongBits(this.minLatitude) ^ (Double.doubleToLongBits(this.minLatitude) >>> 32));
        hash = 97 * hash + (int) (Double.doubleToLongBits(this.minLongitude) ^ (Double.doubleToLongBits(this.minLongitude) >>> 32));
        return hash;
    }

    /**
     * @return the GeoPoint at the horizontal and vertical center of this
     * BoundingBox.
     */
    public Point getCenterPoint() {
        double lat1 = this.minLatitude;
        double lon1 = this.minLongitude;
        double lat2 = this.maxLatitude;
        double lon2 = this.maxLongitude;

        double dLon = Math.toRadians(lon2 - lon1);

        //convert to radians
        lat1 = Math.toRadians(lat1);
        lat2 = Math.toRadians(lat2);
        lon1 = Math.toRadians(lon1);

        double Bx = Math.cos(lat2) * Math.cos(dLon);
        double By = Math.cos(lat2) * Math.sin(dLon);
        double lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + Bx) * (Math.cos(lat1) + Bx) + By * By));
        double lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx);

        return new Point(Math.toDegrees(lat3), Math.toDegrees(lon3));
    }

    /**
     * @return the maximum latitude value of this BoundingBox in degrees.
     */
    public double getMaxLatitude() {
        return this.maxLatitude;
    }

    /**
     * @return the maximum longitude value of this BoundingBox in degrees.
     */
    public double getMaxLongitude() {
        return this.maxLongitude;
    }

    /**
     * @return the minimum latitude value of this BoundingBox in degrees.
     */
    public double getMinLatitude() {
        return this.minLatitude;
    }

    /**
     * @return the minimum longitude value of this BoundingBox in degrees.
     */
    public double getMinLongitude() {
        return this.minLongitude;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("BoundingBox [minLatitude=");
        stringBuilder.append(this.minLatitude);
        stringBuilder.append(", minLongitude=");
        stringBuilder.append(this.minLongitude);
        stringBuilder.append(", maxLatitude=");
        stringBuilder.append(this.maxLatitude);
        stringBuilder.append(", maxLongitude=");
        stringBuilder.append(this.maxLongitude);
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
}
